﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ThreadingHelpers.PeriodicalEngine
{
    public class PeriodicalInvoker<TArgs, TMessage>
    {
        protected static Random Rnd { get; set; }

        public event EventHandler<PeriodicalInvokerRunnerMessageArgs<TMessage>> MessageSent;
        protected virtual void OnMessageSent(TMessage message)
        {
            EventHandler<PeriodicalInvokerRunnerMessageArgs<TMessage>> handlerMessageSent = MessageSent;
            if (handlerMessageSent != null)
            {
                PeriodicalInvokerRunnerMessageArgs<TMessage> args = new PeriodicalInvokerRunnerMessageArgs<TMessage>();
                args.Message = message;
                handlerMessageSent(this, args);
            }
        }



        protected object lock_Status = new object();
        protected volatile PeriodicalInvokerStatus m_Status = PeriodicalInvokerStatus.Stopped;
        public PeriodicalInvokerStatus Status
        {
            get { lock (lock_Status) { return m_Status; } }
            set { lock (lock_Status) { m_Status = value; } }
        }

        protected volatile bool m_IsStatusChanged = false;
        public bool IsStatusChanged
        {
            get { lock (lock_Status) { return m_IsStatusChanged; } }
            set { lock (lock_Status) { m_IsStatusChanged = value; } }
        }

        protected Thread Th { get; set; }

        public IPeriodicalInvokerRunner<TArgs, TMessage> Runner { get; set; }
        public TArgs Args { get; set; }

        /// <summary>
        /// In miliseconds
        /// </summary>
        public int Period { get; set; }
        
        /// <summary>
        /// In miliseconds
        /// </summary>
        public int RandomPause { get; set; }
        
        /// <summary>
        /// In miliseconds
        /// </summary>
        public int StartShift { get; set; }

        /// <summary>
        /// In miliseconds
        /// </summary>
        public int WaitQuant { get; set; }

        public DateTime? StartTime { get; set; }
        public DateTime? EndTime { get; set; }



        static PeriodicalInvoker()
        {
            Rnd = new Random(DateTime.Now.Millisecond);
        }

        public PeriodicalInvoker(IPeriodicalInvokerRunner<TArgs, TMessage> runner)
        {
            Status = PeriodicalInvokerStatus.Stopped;

            Runner = runner;
            Runner.Invoker = this;
        }


        protected PeriodicalInvokerArgs<TArgs, TMessage> Parameters = new PeriodicalInvokerArgs<TArgs, TMessage>();

        private void StartMainLoop(object state)
        {
            PeriodicalInvokerArgs<TArgs, TMessage> parameters = state as PeriodicalInvokerArgs<TArgs, TMessage>;

            IPeriodicalInvokerRunner<TArgs, TMessage> runner = parameters.Runner;
            TArgs args = parameters.Args;

            Thread.Sleep(StartShift);

            while (true)
            {
                if (Status == PeriodicalInvokerStatus.Started)
                {
                    runner.RunPeriodicaly(args);
                }
                else
                {
                    Thread.Sleep(WaitQuant);
                }
                CheckStatus();

                Thread.Sleep(Period);
                CheckStatus();
            }
        }


        protected void CheckStatus()
        {
            if (!IsStatusChanged)
                return;

            switch (Status)
            {
                case PeriodicalInvokerStatus.Pausing :
                    FinishPause();
                    break;
                case PeriodicalInvokerStatus.ReStarting :
                    FinishRestart();
                    break;
                case PeriodicalInvokerStatus.Starting :
                    FinishStart();
                    break;
                case PeriodicalInvokerStatus.Stopping :
                    FinishStop();
                    break;
                case PeriodicalInvokerStatus.ReInitializing :
                    FinishReInit();
                    break;
                default :
                    return;
            }

            IsStatusChanged = false;
        }

        public void Init()
        {
            if (Th != null)
            {
                Th.Abort();
                Th = null;
            }

            Parameters = new PeriodicalInvokerArgs<TArgs, TMessage>()
            {
                Runner = Runner,
                Args = Args
            };

            Runner.MessageSent += new EventHandler<PeriodicalInvokerRunnerMessageArgs<TMessage>>(Runner_MessageSent);

            Th = new Thread(new ParameterizedThreadStart(StartMainLoop));
            Th.Start(Parameters);
        }

        protected void Runner_MessageSent(object sender, PeriodicalInvokerRunnerMessageArgs<TMessage> e)
        {
            OnMessageSent(e.Message);
        }

        public void Start()
        {
            lock (lock_Status)
            {
                Status = PeriodicalInvokerStatus.Started;
                IsStatusChanged = true;
            }
        }

        protected void FinishStart()
        {
            lock (lock_Status)
            {
                //Th.Start(Parameters);

                Status = PeriodicalInvokerStatus.Started;
            }
        }


        public void Pause()
        {
            lock (lock_Status)
            {
                Status = PeriodicalInvokerStatus.Pausing;
                IsStatusChanged = true;
            }
        }

        public void FinishPause()
        {
            lock (lock_Status)
            {
                Th.Suspend();

                Status = PeriodicalInvokerStatus.Paused;
            }
        }
        
        public void Restart()
        {
            lock (lock_Status)
            {
                Status = PeriodicalInvokerStatus.ReStarting;
                IsStatusChanged = true;
            }
        }

        public void FinishRestart()
        {
            lock (lock_Status)
            {
                Th.Resume();

                Status = PeriodicalInvokerStatus.Started;
            }
        }
        
        public void Stop()
        {
            lock (lock_Status)
            {
                Status = PeriodicalInvokerStatus.Stopping;
                IsStatusChanged = true;
            }
        }

        public void FinishStop()
        {
            lock (lock_Status)
            {
                Th.Abort();
                
                Status = PeriodicalInvokerStatus.Stopped;
            }
        }

        public void ReInit()
        {
            lock (lock_Status)
            {
                Status = PeriodicalInvokerStatus.ReInitializing;
            }
        }

        public void FinishReInit()
        {
            lock (lock_Status)
            {
                // to do : implement reinit
                
                Status = PeriodicalInvokerStatus.Stopped;
            }
        }



        #region Helpers

        protected static bool IsTimeOk(DateTime now, DateTime? startTime, DateTime? endTime)
        {
            bool result = true;

            var dt = DateTime.Now;
            DateTime? startDt = startTime == null ? (DateTime?)null : new DateTime(dt.Year, dt.Month, dt.Day, startTime.Value.Hour, startTime.Value.Minute, startTime.Value.Second);
            DateTime? endDt = endTime == null ? (DateTime?)null : new DateTime(dt.Year, dt.Month, dt.Day, endTime.Value.Hour, endTime.Value.Minute, endTime.Value.Second);
            if (startDt != null && endDt != null && startDt.Value > endDt.Value)
                endDt = endDt.Value.AddDays(1);

            result = (startDt == null || startDt <= now) && (endDt == null || now <= endDt);

            return result;
        }

        #endregion Helpers
    }
}
